{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Quickstart to integrate Recommenders in AzureML Designer\n",
    "\n",
    "This notebook shows how to integrate any algorithm in Recommenders library into AzureML Designer. \n",
    "\n",
    "[AzureML Designer](https://docs.microsoft.com/en-us/azure/machine-learning/concept-designer) lets you visually connect datasets and modules on an interactive canvas to create machine learning models. \n",
    "\n",
    "![img](https://recodatasets.blob.core.windows.net/images/designer-drag-and-drop.gif)\n",
    "\n",
    "One of the features of AzureML Designer is that it is possible for developers to integrate any python library to make it available as a module. In this notebook are are going to show how to integrate [SAR](sar_movielens.ipynb) and several other modules in Designer\n",
    "\n",
    "\n",
    "## Installation\n",
    "\n",
    "The first step is to install [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and Module CLI extension. Assuming that you have installed the Recommenders environment `reco_base` as explained in the [SETUP.md](../../SETUP.md), you need to install:\n",
    "```bash\n",
    "conda activate reco_base\n",
    "pip install azure-cli\n",
    "# Uninstall azure-cli-ml (the `az ml` commands)\n",
    "az extension remove -n azure-cli-ml\n",
    "# Install local version of azure-cli-ml (which includes `az ml module` commands)\n",
    "az extension add --source https://azuremlsdktestpypi.azureedge.net/CLI-SDK-Runners-Validation/13082891/azure_cli_ml-0.1.0.13082891-py3-none-any.whl --pip-extra-index-urls https://azuremlsdktestpypi.azureedge.net/CLI-SDK-Runners-Validation/13082891 --yes\n",
    "```\n",
    "\n",
    "## Module implementation\n",
    "\n",
    "The scenario that we are going to reproduce in Designer, as a reference example, is the content of the [SAR quickstart notebook](sar_movielens.ipynb). In it, we load a dataset, split it into train and test sets, train SAR algorithm, predict using the test set and compute several ranking metrics (precision at k, recall at k, MAP and nDCG).\n",
    "\n",
    "For the pipeline that we want to create in Designer, we need to build the following modules:\n",
    "\n",
    "- Stratified splitter\n",
    "- SAR training\n",
    "- SAR prediction\n",
    "- Precision at k\n",
    "- Recall at k\n",
    "- MAP\n",
    "- nDCG\n",
    "\n",
    "The python code is defined with a python entry and a yaml file. All the python entries and yaml files for this pipeline can be found in [reco_utils/azureml/azureml_designer_modules](../../reco_utils/azureml/azureml_designer_modules).\n",
    "\n",
    "\n",
    "### Define python entry\n",
    "\n",
    "To illustrate how a python entry is defined we are going to explain the [precision at k entry](../../reco_utils/azureml/azureml_designer_modules/entries/precision_at_k_entry.py). A simplified version of the code is shown next:\n",
    "\n",
    "```python\n",
    "# Dependencies\n",
    "from azureml.studio.core.data_frame_schema import DataFrameSchema\n",
    "from azureml.studio.core.io.data_frame_directory import (\n",
    "    load_data_frame_from_directory,\n",
    "    save_data_frame_to_directory,\n",
    ")\n",
    "from reco_utils.evaluation.python_evaluation import precision_at_k\n",
    "\n",
    "# First, the input variables of precision_at_k are defined as argparse arguments\n",
    "if __name__ == \"__main__\":\n",
    "    parser = argparse.ArgumentParser()\n",
    "    parser.add_argument(\"--rating-true\", help=\"True DataFrame.\")\n",
    "    parser.add_argument(\"--rating-pred\", help=\"Predicted DataFrame.\")\n",
    "    parser.add_argument(\n",
    "        \"--col-user\", type=str, help=\"A string parameter with column name for user.\"\n",
    "    )\n",
    "    # ... more arguments\n",
    "    args, _ = parser.parse_known_args()\n",
    "\n",
    "    # This module has two main inputs from the canvas, the true and predicted labels\n",
    "    # they are loaded into the runtime as a pandas DataFrame\n",
    "    rating_true = load_data_frame_from_directory(args.rating_true).data\n",
    "    rating_pred = load_data_frame_from_directory(args.rating_pred).data\n",
    "\n",
    "    # The python function is instantiated and the computation is performed\n",
    "    eval_precision = precision_at_k(rating_true, rating_pred)\n",
    "    \n",
    "    # To output the result to Designer, we write it as a DataFrame\n",
    "    score_result = pd.DataFrame({\"precision_at_k\": [eval_precision]})\n",
    "    save_data_frame_to_directory(\n",
    "        args.score_result,\n",
    "        score_result,\n",
    "        schema=DataFrameSchema.data_frame_to_dict(score_result),\n",
    "    )\n",
    "```\n",
    "\n",
    "\n",
    "### Define module specification yaml\n",
    "\n",
    "Once we have the python entry, we need to create the yaml file that will interact with Designer, [precision_at_k.yaml](../../reco_utils/azureml/azureml_designer_modules/module_specs/precision_at_k.yaml).\n",
    "\n",
    "```yaml\n",
    "moduleIdentifier: \n",
    "  namespace: microsoft.com/cat\n",
    "  moduleName: Precision at K\n",
    "  moduleVersion: 1.1.0\n",
    "description: \"Precision at K metric from Recommenders repo: https://github.com/Microsoft/Recommenders.\"\n",
    "metadata:\n",
    "  annotations:\n",
    "    tags: [\"Recommenders\", \"Metrics\"]\n",
    "inputs:\n",
    "- name: Rating true\n",
    "  type: DataFrameDirectory\n",
    "  description: True DataFrame.\n",
    "- name: Rating pred\n",
    "  type: DataFrameDirectory\n",
    "  description: Predicted DataFrame.\n",
    "- name: User column\n",
    "  type: String\n",
    "  default: UserId\n",
    "  description: Column name of user IDs.\n",
    "- name: Item column\n",
    "  type: String\n",
    "  default: MovieId\n",
    "  description: Column name of item IDs.\n",
    "- name: Rating column\n",
    "  type: String\n",
    "  default: Rating\n",
    "  description: Column name of ratings.\n",
    "- name: Prediction column\n",
    "  type: String\n",
    "  default: prediction\n",
    "  description: Column name of predictions.\n",
    "- name: Relevancy method\n",
    "  type: String\n",
    "  default: top_k\n",
    "  description: method for determining relevancy ['top_k', 'by_threshold'].\n",
    "- name: Top k\n",
    "  type: Integer\n",
    "  default: 10\n",
    "  description: Number of top k items per user.\n",
    "- name: Threshold\n",
    "  type: Float\n",
    "  default: 10.0\n",
    "  description: Threshold of top items per user.\n",
    "outputs:\n",
    "- name: Score\n",
    "  type: DataFrameDirectory\n",
    "  description: Precision at k (min=0, max=1).\n",
    "implementation:\n",
    "  container:\n",
    "    amlEnvironment:\n",
    "      python:\n",
    "        condaDependenciesFile: sar_conda.yaml\n",
    "    additionalIncludes:\n",
    "      - ../../../\n",
    "    command: [python, reco_utils/azureml/azureml_designer_modules/entries/precision_at_k_entry.py]\n",
    "    args:\n",
    "    - --rating-true\n",
    "    - inputPath: Rating true\n",
    "    - --rating-pred\n",
    "    - inputPath: Rating pred\n",
    "    - --col-user\n",
    "    - inputValue: User column\n",
    "    - --col-item\n",
    "    - inputValue: Item column\n",
    "    - --col-rating\n",
    "    - inputValue: Rating column\n",
    "    - --col-prediction\n",
    "    - inputValue: Prediction column\n",
    "    - --relevancy-method\n",
    "    - inputValue: Relevancy method\n",
    "    - --k\n",
    "    - inputValue: Top k\n",
    "    - --threshold\n",
    "    - inputValue: Threshold\n",
    "    - --score-result\n",
    "    - outputPath: Score\n",
    "```\n",
    "\n",
    "In the yaml file we can see a number of sections. The heading defines attributes like name, version or description. In the section inputs, all inputs are defined. The two main dataframes have ports, which can be connected to other modules. The inputs without port appear in a canvas menu. The output is defined as a DataFrame as well. The last section, implementation, defines the conda environment, the associated python entry and the arguments to the python file.\n",
    "\n",
    "\n",
    "## Module Registration\n",
    "\n",
    "Once the code is implemented, we need to register it as an AzureML Designer custom module. The registration can be performed following these simple steps:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!az login"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!az account set -s \"Your subscription name\"\n",
    "!az ml folder attach -w \"Your workspace name\" -g \"Your resource group name\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import tempfile\n",
    "import shutil\n",
    "import subprocess"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start to register module spec: map.yaml ...\n",
      "Done.\n",
      "Start to register module spec: ndcg.yaml ...\n",
      "Done.\n",
      "Start to register module spec: precision_at_k.yaml ...\n",
      "Done.\n",
      "Start to register module spec: recall_at_k.yaml ...\n",
      "Done.\n",
      "Start to register module spec: sar_conda.yaml ...\n",
      "Done.\n",
      "Start to register module spec: sar_score.yaml ...\n",
      "Done.\n",
      "Start to register module spec: sar_train.yaml ...\n",
      "Done.\n",
      "Start to register module spec: stratified_splitter.yaml ...\n",
      "Done.\n"
     ]
    }
   ],
   "source": [
    "# Regsiter modules with spec via Azure CLI\n",
    "root_path = os.path.abspath(os.path.join(os.getcwd(), \"../../\"))\n",
    "specs_folder = os.path.join(root_path, \"reco_utils/azureml/azureml_designer_modules/module_specs\")\n",
    "github_prefix = 'https://github.com/microsoft/recommenders/blob/master/reco_utils/azureml/azureml_designer_modules/module_specs/'\n",
    "specs = os.listdir(specs_folder)\n",
    "for spec in specs:\n",
    "    spec_path = github_prefix + spec\n",
    "    print(f\"Start to register module spec: {spec} ...\")\n",
    "    subprocess.run(f\"az ml module register --spec-file {spec_path}\", shell=True)\n",
    "    print(f\"Done.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Running Recommenders in AzureML Designer\n",
    "\n",
    "Once the modules are registered, they will appear in the canvas as the module `Recommenders`. There you will be able to create a pipeline like this:\n",
    "\n",
    "![img](https://recodatasets.blob.core.windows.net/images/azureml_designer_sar_precisionatk.png)\n",
    "\n",
    "Now, thanks to AzureML Designer, users can compute the latest state of the art algorithms in recommendation systems without writing a line of python code.\n",
    "\n",
    "## References\n",
    "\n",
    "1. [AzureML Designer documentation](https://docs.microsoft.com/en-us/azure/machine-learning/concept-designer)\n",
    "1. [Tutorial: Prediction of automobile price](https://docs.microsoft.com/en-us/azure/machine-learning/tutorial-designer-automobile-price-train-score)\n",
    "1. [Tutorial: Classification of time flight delays](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-designer-sample-classification-flight-delay)\n",
    "1. [Tutorial: Text classification of company categories](https://docs.microsoft.com/en-us/azure/machine-learning/how-to-designer-sample-text-classification)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}